namespace Langexplr.Abc

open System
open System.IO
open Langexplr.Abc


type AbcNamespace = { kind : NamespaceKind ; nameIndex : int; }
  with
     static member ReadFrom(input : BinaryReader) =
      let theKind = int(input.ReadByte()) 
       in
         if (List.exists(fun x -> x = theKind) [int(NamespaceKind.Namespace);
                                                int(NamespaceKind.PackageNamespace);
                                                int(NamespaceKind.PackageInternalNs);
                                                int(NamespaceKind.ProtectedNamespace);
                                                int(NamespaceKind.ExplicitNamespace);
                                                int(NamespaceKind.StaticProtectedNs);
                                                int(NamespaceKind.PrivateNs)]) then
            { kind = enum<NamespaceKind>(theKind);
              nameIndex = int(TypeEncodingUtilities.ReadU30Value(input)) }
         else
            raise (new System.InvalidOperationException(String.Format("Namespace kind invalid {0}",theKind)))
            
     member x.WriteTo(output : BinaryWriter) =
        output.Write(byte(x.kind));
        TypeEncodingUtilities.WriteU30Value(uint32(x.nameIndex),output)
     override this.ToString() = sprintf "Namespace: kind = %s, name = %d" (this.kind.ToString()) this.nameIndex
end

type AbcNsSet = { count : int ; ns : int array }
  with
     static member ReadFrom(input : BinaryReader) =
         let theCount = int(TypeEncodingUtilities.ReadU30Value(input))
           in
               { count = theCount;
                 ns = [|for _ in 1 .. theCount->  int(TypeEncodingUtilities.ReadU30Value(input)) |]; }
     member this.WriteTo(output : BinaryWriter) =        
        TypeEncodingUtilities.WriteU30Value(uint32(this.count),output);
        Array.iter (fun (nsIndex:int) -> TypeEncodingUtilities.WriteU30Value(uint32(nsIndex),output)) this.ns

     override this.ToString() = 
                  sprintf "Namspace set: count = %d : [%s]" 
                          this.count 
                          (System.String.Join("," ,Array.map (fun x -> x.ToString()) this.ns))
end

// Multiname type 
type MultiNameInfo = 
    | QName of int * int
    | QNameA of int * int
    | RTQName of int
    | RTQNameA of int
    | RTQNameL 
    | RTQNameLA
    | Multiname of int * int
    | MultinameA of int * int
    | MultinameL of int
    | MultinameLA of int
  with
  
  override this.ToString() = 
     match this with
       | QName(ns,name) ->  System.String.Format("QName {0} {1}",ns,name);
       | QNameA(ns,name)  ->  System.String.Format("QNameA {0} {1}",ns,name);
       | RTQName   ns -> System.String.Format("RTQName {0}",ns);
       | RTQNameA  ns -> System.String.Format("RTQNameA {0}",ns);
       | RTQNameL -> "RTQNameL";
       | RTQNameLA -> "RTQNameLA";
       | Multiname(name,ns) ->  System.String.Format("Multiname {0} {1}",ns,name);
       | MultinameA(name,ns) -> System.String.Format("MultinameA {0} {1}",ns,name);
       | MultinameL ns -> System.String.Format("MultinameL {0}",ns);
       | MultinameLA  ns -> System.String.Format("MultinameLA {0}",ns);


         
  member this.Print(cpool:ConstantPoolInfo) =
     match this with
       | QName(ns,name) ->  System.Console.WriteLine("QName {0} {1}",cpool.NsNameForIndex(ns),cpool.NameForIndex(name));
       | QNameA(ns,name)  ->  System.Console.WriteLine("QNameA {0} {1}",ns,cpool.NameForIndex(name));
       | RTQName   ns -> System.Console.WriteLine("RTQName {0}",ns);
       | RTQNameA  ns -> System.Console.WriteLine("RTQNameA {0}",ns);
       | RTQNameL -> System.Console.WriteLine("RTQNameL");
       | RTQNameLA -> System.Console.WriteLine("RTQNameLA");
       | Multiname(name,ns) ->  System.Console.WriteLine("Multiname {0} {1}",ns,cpool.NameForIndex(name));
       | MultinameA(name,ns) -> System.Console.WriteLine("MultinameA {0} {1}",ns,cpool.NameForIndex(name));
       | MultinameL ns -> System.Console.WriteLine("MultinameL {0}",ns);
       | MultinameLA  ns -> System.Console.WriteLine("MultinameLA {0}",ns);
       
       
  static member ReadFrom(input : BinaryReader) =
     match int(input.ReadByte()) with
        | 0x07 -> QName(int(TypeEncodingUtilities.ReadU30Value(input)), int(TypeEncodingUtilities.ReadU30Value(input)))
        | 0x0D -> QNameA(int(TypeEncodingUtilities.ReadU30Value(input)), int(TypeEncodingUtilities.ReadU30Value(input)))
        | 0x0F -> RTQName(int(TypeEncodingUtilities.ReadU30Value(input)))
        | 0x10 -> RTQNameA(int(TypeEncodingUtilities.ReadU30Value(input)))
        | 0x11 -> RTQNameL
        | 0x12 -> RTQNameLA
        | 0x09 -> Multiname(int(TypeEncodingUtilities.ReadU30Value(input)), int(TypeEncodingUtilities.ReadU30Value(input)))
        | 0x0E -> MultinameA(int(TypeEncodingUtilities.ReadU30Value(input)), int(TypeEncodingUtilities.ReadU30Value(input)))
        | 0x1B -> MultinameL(int(TypeEncodingUtilities.ReadU30Value(input)))
        | 0x1C -> MultinameLA(int(TypeEncodingUtilities.ReadU30Value(input)))
        | v -> raise (new System.InvalidOperationException(String.Format("Unexpected value for multiname kind {0}",v)))
        
(* override  this.Equals(obj : System.Object) =
     if (obj :? MultiNameInfo) then     
        match (this,(obj :?> MultiNameInfo)) with
             | (QName(x1,y1),QName(x2,y2)) -> x1 = x2 && y1 = y2
             | (QNameA(x1,y1),QNameA(x2,y2)) -> x1 = x2 && y1 = y2
             | (RTQName(x1), RTQName(x2)) -> x1 = x2
             | (RTQNameA(x1), RTQNameA(x2)) -> x1 = x2
             | RTQNameL,RTQNameL -> true
             | RTQNameLA,RTQNameLA -> true
             | (Multiname(x1,y1),Multiname(x2,y2)) -> x1 = x2 && y1 = y2
             | (MultinameA(x1,y1),MultinameA(x2,y2)) -> x1 = x2 && y1 = y2
             | (MultinameL x,MultinameL y) -> x = y
             | (MultinameLA x,MultinameLA y) -> x = y
             | _ -> false
     else
        false   *)
        
  member this.WriteTo(output:BinaryWriter) =
      match this with
        | QName(ns,name) ->
           output.Write(byte(0x07));
           TypeEncodingUtilities.WriteU30Value(uint32(ns),output);
           TypeEncodingUtilities.WriteU30Value(uint32(name),output) 
        | QNameA(ns,name) ->
           output.Write(byte(0x0D));
           TypeEncodingUtilities.WriteU30Value(uint32(ns),output);
           TypeEncodingUtilities.WriteU30Value(uint32(name),output) 
        | RTQName(name) ->
           output.Write(byte(0x0F));           
           TypeEncodingUtilities.WriteU30Value(uint32(name),output) 
        | RTQNameA(name) ->
           output.Write(byte(0x10));           
           TypeEncodingUtilities.WriteU30Value(uint32(name),output) 
        | RTQNameL ->
           output.Write(byte(0x11))           
        | RTQNameLA ->
           output.Write(byte(0x12))
        | Multiname(name,nsSet) ->
           output.Write(byte(0x09));
           
           // Ther orde of the following instructions was changed from what
           // it says on avm2overview.pdf  p.24 . Because data generated by asc.jar
           // seems to indicate the contrary
           TypeEncodingUtilities.WriteU30Value(uint32(nsSet),output);
           TypeEncodingUtilities.WriteU30Value(uint32(name),output);
           
        | MultinameA(name,nsSet) ->
           output.Write(byte(0x0E));
           TypeEncodingUtilities.WriteU30Value(uint32(name),output);
           TypeEncodingUtilities.WriteU30Value(uint32(nsSet),output) 
        | MultinameL(nsSet) ->
           output.Write(byte(0x1B));
           TypeEncodingUtilities.WriteU30Value(uint32(nsSet),output) 
        | MultinameLA(nsSet) ->
           output.Write(byte(0x1C));
           TypeEncodingUtilities.WriteU30Value(uint32(nsSet),output) 
end    
and
 ConstantPoolInfo(integers  : int array, 
                  uinteger  : uint32 array, 
                  doubles : double array, 
                  stringInfo : string array, 
                  namespaceInfo : AbcNamespace array, 
                  nsSetInfo : AbcNsSet array, 
                  multiname : MultiNameInfo array) = 
  class
  
     member this.Integers with get() = integers
     member this.UnsignedIntegers with get() = uinteger
     member this.Doubles with get() = doubles
     member this.Strings with get() = stringInfo
     member this.Namespaces with get() = namespaceInfo
     member this.NamespaceSets with get() = nsSetInfo
     member this.Multinames with get() = multiname
     
     member this.GetInt(index: int) =
        integers.[index-1]
     
     member this.GetUInt(index: int) =
        uinteger.[index-1]
     
     member this.GetDouble(index: int) =
        doubles.[index-1]
     
     member this.GetString(index : int) =
       match index with
         | 0 -> "*"
         | index -> stringInfo.[index-1]
     
     member this.SolveNamespaceName(namespaceIndex : int) = 
         let nsInfo = namespaceInfo.[namespaceIndex-1]
            in   Ns(this.GetString(nsInfo.nameIndex),nsInfo.kind)
            
     member this.SolveNsSet(nsSetIndex : int) : Ns array =
        let nsSet = nsSetInfo.[nsSetIndex-1]
        in (Array.map (fun nsIndex -> this.SolveNamespaceName(nsIndex))  nsSet.ns)
     
     
     member this.SolveTypeName(multinameIndex : int) =
        match multinameIndex with
        | 0 -> SQualifiedName("*")
        | other -> this.SolveMultiName(other)
     
     member this.SolveMultiName(multinameIndex : int) =
      if multinameIndex <> 0 then
        let mn = multiname.[multinameIndex-1]
        in
           match mn with
           | QName(0,nameIndex) -> SQualifiedName (this.GetString(nameIndex))
           | QNameA(0,nameIndex) -> SQualifiedName (this.GetString(nameIndex))
           | QName(nsIndex,nameIndex) -> CQualifiedName (this.SolveNamespaceName(nsIndex),this.GetString(nameIndex))
           | QNameA(nsIndex,nameIndex) -> CQualifiedName (this.SolveNamespaceName(nsIndex),this.GetString(nameIndex))
           | Multiname(nameIndex,nsSetIndex) -> MQualifiedName(this.SolveNsSet(nsSetIndex),this.GetString(nameIndex))
           | _ -> raise (new System.InvalidOperationException("Unknown name kind"))
      else
        SQualifiedName("*")

     
     member this.NsNameForIndex(nsIdx) = 
        match namespaceInfo.[nsIdx-1] with
        { kind = kind; nameIndex = nameIndex } -> 
            String.Format("({0}){1}",kind,this.NameForIndex(nameIndex))
        
           
     
     member this.NameForIndex(idx) = stringInfo.[idx-1]
     
     member this.DumpNamespaceInfo() =
        Array.iter (fun {kind = x;  nameIndex = idx} -> 
                        System.Console.WriteLine("\tn_s = {0}",stringInfo.[idx-1]) ) namespaceInfo
     member this.DumpNSSetInfo() =
        Array.iter (fun {count = c ; ns = nsArray } -> 
                        System.Console.WriteLine(
                           "\ns = {0} --> {1}", c,
                           String.Join(".", 
                                       (Array.map (fun idx -> match namespaceInfo.[idx-1] with {nameIndex = nsStringIdx} -> stringInfo.[nsStringIdx - 1] ) 
                                       nsArray))))  
                   nsSetInfo
     
     static member ReadFrom(input : BinaryReader) =
        let intCount = int(TypeEncodingUtilities.ReadU30Value(input)) in
        let integers = [| for _ in 1 .. intCount - 1 -> int(TypeEncodingUtilities.ReadU30Value(input)) |] in
        let uintCount = int(TypeEncodingUtilities.ReadU30Value(input)) in
        let uinteger = [| for _ in 1 .. uintCount - 1 -> uint32(TypeEncodingUtilities.ReadU30Value(input)) |] in
        let doubleCount = int(TypeEncodingUtilities.ReadU30Value(input)) in
        let doubles = [| for _ in 1 .. doubleCount - 1 -> input.ReadDouble() |] in
        let stringCount = int(TypeEncodingUtilities.ReadU30Value(input)) in
        let stringInfo = [| for _ in 1 .. stringCount - 1 -> TypeEncodingUtilities.ReadStringInfo(input) |] in
        let namespaceCount = int(TypeEncodingUtilities.ReadU30Value(input)) in
        let namespaceInfo = [| for _ in 1 .. namespaceCount - 1 -> AbcNamespace.ReadFrom(input) |] in
        let nsSetCount = int(TypeEncodingUtilities.ReadU30Value(input)) in
        let nsSetInfo = [| for _ in 1 .. nsSetCount - 1 -> AbcNsSet.ReadFrom(input) |] in
        let multiNameCount = int(TypeEncodingUtilities.ReadU30Value(input)) in
        let multiname = [| for _ in 1 .. multiNameCount - 1 -> MultiNameInfo.ReadFrom(input) |] in        
        ConstantPoolInfo(integers,uinteger,doubles,stringInfo,namespaceInfo,nsSetInfo,multiname)
     
     member this.WriteTo(output:BinaryWriter) =
        TypeEncodingUtilities.WriteU30Value(uint32(integers.Length+1),output);
        Array.iter (fun (aValue:int) -> TypeEncodingUtilities.WriteU30Value(uint32(aValue),output)) integers;
        TypeEncodingUtilities.WriteU30Value(uint32(uinteger.Length+1),output);
        Array.iter (fun (aValue:uint32) -> TypeEncodingUtilities.WriteU30Value(uint32(aValue),output)) uinteger;
        TypeEncodingUtilities.WriteU30Value(uint32(doubles.Length+1),output);
        Array.iter (fun (aValue:double) -> output.Write(aValue)) doubles;
        TypeEncodingUtilities.WriteU30Value(uint32(stringInfo.Length+1),output);
        Array.iter (fun (aValue:string) -> TypeEncodingUtilities.WriteStringInfo(aValue,output)) stringInfo;
        TypeEncodingUtilities.WriteU30Value(uint32(namespaceInfo.Length+1),output);
        Array.iter (fun (aValue:AbcNamespace) -> aValue.WriteTo(output)) namespaceInfo;
        TypeEncodingUtilities.WriteU30Value(uint32(nsSetInfo.Length+1),output);
        Array.iter (fun (aValue:AbcNsSet) -> aValue.WriteTo(output)) nsSetInfo;
        TypeEncodingUtilities.WriteU30Value(uint32(multiname.Length+1),output);
        Array.iter (fun (aValue:MultiNameInfo) -> aValue.WriteTo(output)) multiname;
end
